/*
 * SchedulerManager.java
 *
 * Created on April 28, 2005, 11:52 AM
 */

package aa.core;

import java.util.Hashtable;
import java.util.Enumeration;
import java.util.NoSuchElementException;

import aa.util.Queue;
import aa.core.CyberOrgManager;


/**
 * This class is the actor scheduler, and it schedules
 * all actors on this platform
 * 
 * @author  xinghuizhao
 *

 *
 */
public class SchedulerManager extends Thread{
    
    private Hashtable m_htThreadTimeslice; //hashtable which map actors to timeslices
    
    /*As of Nov11 2005, we get rid of totaltime for individual actors.*/
    //private Hashtable m_htThreadTotaltime; //hashtable which maps actors to total time they could get
    
    private Queue m_queueThreads; //the threads queue
    
    private Thread m_threadCurrent; //the thread that is selected to be executed
    
    private long m_lSmallestSlice, m_lLargestSlice; //parameters for granularity control
    
    private boolean m_bGranularity; //if there's granularity control, it is true
    
    private CyberOrgManager m_comCyberOrgManager; //handler for CyberOrgManager
    
    private Long m_longBeginSign;
    
    
    /** Creates a new instance of SchedulerManager */
    public SchedulerManager(CyberOrgManager p_comCyberOrgManager) {
        
        m_htThreadTimeslice = new Hashtable();
        
        m_queueThreads = new Queue();
        
        m_longBeginSign = new Long(0);
        
        m_queueThreads.add(m_longBeginSign);
        
        m_comCyberOrgManager=p_comCyberOrgManager;
        
        m_threadCurrent = Thread.currentThread();
        
        //
        //declare this thread as a daemon thread
        //
        
        setDaemon(true);
        
        System.out.println("% Scheduler Manager started.");
        
        
    }
    
    /* Initial Scheduler with smallest and largest time slice*/
    public SchedulerManager(long p_lSmallestSlice, long p_lLargestSlice) {
        
        m_htThreadTimeslice = new Hashtable();
        
        m_queueThreads = new Queue();
        
        m_threadCurrent = Thread.currentThread();
        
        m_lSmallestSlice = p_lSmallestSlice;
        
        m_lLargestSlice = p_lLargestSlice;
        
        //
        //declare this thread as a daemon thread
        //
        
        setDaemon(true);
        
        System.out.println("% Scheduler Manager started.");
        
        
    }
    
    /**
     * Uninitializes this program.
     *
     * This method is called by the Platform.
     */
    protected void uninit(){
        
        System.out.println("% Scheduler Manager ended.");
        
    }
    
    /**
     * Remove a thread from the thread queue for scheduling.
     *
     * return the first thread of thread queue
     *
     *@return The first thread of thread queue
     */
    
    private Thread remove(){
        
        try{
            
            Thread thread = (Thread)m_queueThreads.remove();
            
            return thread;
            
        }catch (NoSuchElementException e){
            
            return null;
            
        }
    }
    
    /**
     *  Remove specific thread <br>
     * This method is called when migration happens
     * in order to remove the migrating actors from the local scheduler
     * 
     * @param p_thread The thread being removed
     */
    
    public void remove(Thread p_thread){
        
        boolean rem = m_queueThreads.remove(p_thread);
        if ((!rem)&&(m_threadCurrent != null)){
            if (m_threadCurrent.equals(p_thread)){
                //synchronized (m_threadCurrent){
                    m_threadCurrent = null;
                //}
            }
        }
        
        //System.out.println("Remove "+p_thread+" from hashtables...");
        
        
        m_htThreadTimeslice.remove(p_thread);
        
        //System.out.println("Removed "+p_thread+" from hashtables...");
        
    }
    
    /** 
     * 
     * This method inserts a thread into the thread queue for thread scheduling. <br>
     * p_thread: the reference to a thread to be inserted to the thread queue <br>
     * p_lTimeslice: timeslice in milliseconds that the corresponding thread 
     * is assigned in one scheduling cycle
     *
     * @param p_thread The reference to a thread to be inserted to the thread queue
     * @param p_lTimeslice Timeslice in milliseconds that the corresponding thread 
     * is assigned in one scheduling cycle
     */
    
    public void insert(Thread p_thread, long p_lTimeslice){
        
        //System.out.println("^^^^^^^^^^^^^^^^^ p_thread:"+p_thread+" timeslice"+p_lTimeslice);
        
        m_htThreadTimeslice.put(p_thread, new Long(p_lTimeslice)); 
        
        
        //for debug
        
        /*if (p_lTimeslice >= 0){
            m_htThreadTimeslice.put(p_thread, new Long(100)); 
        }*/
        
        //for debug
        
        //System.out.println("Someone is put value in ThreadTotaltime...");
     
        
        
        m_queueThreads.insert(p_thread); //insert thread
        //System.out.println("Insertation done!");
        
    }
    
    /**
     * Called by CyberOrgManager when a cyberorg is reactived.
     * @param p_thread The thread being inserted
     */
    public void insert(Thread p_thread){
        m_queueThreads.insert(p_thread);
    }
    
    
    /** 
     * Look up the hashtable to find out the time slice of specific actor 
     * 
     * @param p_thread The reference to a thread
     * @return The corresponding time slice allocated the the given thread
     */
    public long getTimeSlice(Thread p_thread){
        try{
            //System.out.println("scheduler manager try to get res");
            Long res = (Long)m_htThreadTimeslice.get(p_thread);
            //System.out.println("got");
            return res.longValue();
        }catch(NullPointerException e){
            return 0;
        }
    }
    
    /*public long getTotalTime(Thread p_thread){
        
        System.out.println(Thread.currentThread()+" get element in Totaltime hashtable..");
        Long total = (Long)m_htThreadTotaltime.get(p_thread);
        System.out.println("got element ");
        return total.longValue();
    }*/
    
    
    /**
     * Reallocates resource for specific thread
     * @param p_thread The thread
     * @param p_lTimeSlice The time slice being allocated to the given thread
     */
    public void resAlloc(Thread p_thread,long p_lTimeSlice){
        // if the thread is already in the queue, just update table
        //System.out.println("resAlloc is putting something into hashtable.");
        if (m_queueThreads.contains(p_thread)){
            Long rateAssign = new Long(p_lTimeSlice);
            
            m_htThreadTimeslice.put(p_thread,rateAssign);
            
        }
        /* check if the specified thread is the currently running thread...
          if it is, reallocate resource; if it is not, which means we don't
         have specified thread in our queue , insert it to the queue*/
        else if (!(m_threadCurrent == null)){
            
            //synchronized (m_threadCurrent){
                if (m_threadCurrent.equals(p_thread)){
                    Long rateAssign = new Long(p_lTimeSlice);
                   
                    m_htThreadTimeslice.put(p_thread,rateAssign);
                    
                }
                else{
                    insert(p_thread,p_lTimeSlice);
                }
            //}
            
        }
        /* if the thread is not in the queue, which means it may run out of 
         * resource already, so we insert it into the queue again*/
        else{
            insert(p_thread,p_lTimeSlice);
        }
        //System.out.println("resAlloc has done the manipulate.");
    }
    
    
    /**
     *  
     * In granularity control, this method is called to enlarge the time slices for
     * each actor using a specific factor
     *
     * @param p_lFactor The factor used to scale up the granularity control
     */
    
    public void scaleup(long p_lFactor){
        
        try{
            synchronized(m_htThreadTimeslice){
                for(Enumeration Allkeys=m_htThreadTimeslice.keys();Allkeys.hasMoreElements();){
                    Thread t=(Thread)Allkeys.nextElement();
                    Long t_time=(Long)m_htThreadTimeslice.get(t);
                    m_htThreadTimeslice.put(t, new Long(t_time.longValue()*p_lFactor));
                }
            }
        }catch(Exception e){
            System.err.println("Failed in scaling up all time slices. "+e);
        }
        
    }
    
    /** 
     * The main loop
     *
     */
    
    public void run(){
        
        long totalcharge=0;
        
        while(true){
            //System.out.println("loop....");
            
            if (m_queueThreads.size() == 1){
                //System.out.println("scheduler: queue size is 0...");
                try{
                    synchronized(m_queueThreads){
                        m_queueThreads.wait();
                    }
                }catch (Exception e){
                    System.out.println("trying to wait in scheduler..."+e);
                }
                
            }
            
            
            
            
            Object nextElement = m_queueThreads.remove();
            if (nextElement.equals(m_longBeginSign)){
                totalcharge+=m_comCyberOrgManager.updateTotalTicks();
                //System.out.println("charge: "+totalcharge);
                m_queueThreads.add(nextElement);
            }
            
            else{
            
                //System.out.println("scheduler: try to get lock on m_threadCurrent...");
                
                synchronized (m_threadCurrent){
                    //System.out.println("scheduler:  get lock on m_threadCurrent"+m_threadCurrent);
                
                
                    try{
                    
                        m_threadCurrent = (Thread)nextElement;
                        //System.out.println("now we get: "+m_threadCurrent);
                        if (m_threadCurrent == null) {

                            continue;
                        }
                    
                    
                    
                    }catch (Exception e){
                        System.out.println("NPException...size:"+m_queueThreads.size());
                        continue;
                    }

                    //if (m_threadCurrent == null){
                        //m_threadCurrent = Thread.currentThread();
                        //continue;
                    //}
                }

                //System.out.println("next we'll search the hashtable....");

                Long longtimeslice = (Long) m_htThreadTimeslice.get(m_threadCurrent);

                long timeslice = longtimeslice.longValue();

                //System.out.println("resume in scheduler..."+m_threadCurrent+ "ts: "+timeslice);

                m_threadCurrent.resume();



                try{
                    //System.out.println("scheduler: try to get lock of m_queueThread and wait for "+timeslice);
                    synchronized(m_queueThreads){
                        //System.out.println("got lock of m_queueThread ");
                        m_queueThreads.wait(timeslice);

                    }
                }catch (InterruptedException ite){
                    System.out.println("Exception in Scheduler...");
                }

                try{

                    //System.out.println("scheduler: what is threadCurrent? "+m_threadCurrent);
                    if (m_threadCurrent.isAlive()){

                        //System.out.println("scheduler:"+m_threadCurrent +" is alive" );

                        synchronized(m_htThreadTimeslice){
                            m_threadCurrent.suspend();
                        }


                        //System.out.println("suspended it ! try to insert back");

                        m_queueThreads.insert(m_threadCurrent);

                        //System.out.println("inserted back to queue");


                    }

                }catch (NullPointerException e){

                    //System.out.println("scheduler: NullPointerException "+e);
                    m_threadCurrent = Thread.currentThread();
                }
                
                
                
            
            }     
                
        }            
            
            
    }
        
        
}
    
    
    
    

